/*
 * Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <arch_helpers.h>
#include <debug.h>
#include <mmio.h>
#include <psci.h>

#include "uniphier.h"

#define UNIPHIER_ROM_RSV0        0x59801200

#define UNIPHIER_SLFRSTSEL        0x61843010
#define   UNIPHIER_SLFRSTSEL_MASK        (0x3 << 0)
#define UNIPHIER_SLFRSTCTL        0x61843014
#define   UNIPHIER_SLFRSTCTL_RST        (1 << 0)

#define MPIDR_AFFINITY_INVALID        ((u_register_t)-1)

uintptr_t uniphier_sec_entrypoint;

void uniphier_warmboot_entrypoint(void);
void __dead2 uniphier_fake_pwr_down(void);
u_register_t uniphier_holding_pen_release;
static int uniphier_psci_scp_mode;

static int uniphier_psci_pwr_domain_on(u_register_t mpidr)
{
    uniphier_holding_pen_release = mpidr;
    flush_dcache_range((uint64_t)&uniphier_holding_pen_release,
               sizeof(uniphier_holding_pen_release));

    mmio_write_64(UNIPHIER_ROM_RSV0,
              (uint64_t)&uniphier_warmboot_entrypoint);
    sev();

    return PSCI_E_SUCCESS;
}

static void uniphier_psci_pwr_domain_off(const psci_power_state_t *target_state)
{
    uniphier_gic_cpuif_disable();
}

static void uniphier_psci_pwr_domain_on_finish(
                    const psci_power_state_t *target_state)
{
    uniphier_gic_pcpu_init();
    uniphier_gic_cpuif_enable();

    uniphier_cci_enable();
}

static void __dead2 uniphier_psci_pwr_domain_pwr_down_wfi(
                    const psci_power_state_t *target_state)
{
    /*
     * The Boot ROM cannot distinguish warn and cold resets.
     * Instead of the CPU reset, fake it.
     */
    uniphier_holding_pen_release = MPIDR_AFFINITY_INVALID;
    flush_dcache_range((uint64_t)&uniphier_holding_pen_release,
               sizeof(uniphier_holding_pen_release));

    uniphier_fake_pwr_down();
}

static void uniphier_self_system_reset(void)
{
    mmio_clrbits_32(UNIPHIER_SLFRSTSEL, UNIPHIER_SLFRSTSEL_MASK);
    mmio_setbits_32(UNIPHIER_SLFRSTCTL, UNIPHIER_SLFRSTCTL_RST);
}

static void __dead2 uniphier_psci_system_off(void)
{
    if (uniphier_psci_scp_mode) {
        uniphier_scp_system_off();
    } else {
        NOTICE("SCP is disabled; can't shutdown the system.\n");
        NOTICE("Resetting the system instead.\n");
        uniphier_self_system_reset();
    }

    wfi();
    ERROR("UniPhier System Off: operation not handled.\n");
    panic();
}

static void __dead2 uniphier_psci_system_reset(void)
{
    if (uniphier_psci_scp_mode)
        uniphier_scp_system_reset();
    else
        uniphier_self_system_reset();

    wfi();
    ERROR("UniPhier System Reset: operation not handled.\n");
    panic();
}

static const struct plat_psci_ops uniphier_psci_ops = {
    .pwr_domain_on = uniphier_psci_pwr_domain_on,
    .pwr_domain_off = uniphier_psci_pwr_domain_off,
    .pwr_domain_on_finish = uniphier_psci_pwr_domain_on_finish,
    .pwr_domain_pwr_down_wfi = uniphier_psci_pwr_domain_pwr_down_wfi,
    .system_off = uniphier_psci_system_off,
    .system_reset = uniphier_psci_system_reset,
};

int plat_setup_psci_ops(uintptr_t sec_entrypoint,
            const struct plat_psci_ops **psci_ops)
{
    uniphier_sec_entrypoint = sec_entrypoint;
    flush_dcache_range((uint64_t)&uniphier_sec_entrypoint,
               sizeof(uniphier_sec_entrypoint));

    uniphier_psci_scp_mode = uniphier_scp_is_running();
    flush_dcache_range((uint64_t)&uniphier_psci_scp_mode,
               sizeof(uniphier_psci_scp_mode));

    if (uniphier_psci_scp_mode)
        uniphier_scp_open_com();

    *psci_ops = &uniphier_psci_ops;

    return 0;
}
